﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace WPNextTools.NControls
{
    [TemplateVisualState(Name = PullDownToRefreshPanel.InactiveVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)]
    [TemplateVisualState(Name = PullDownToRefreshPanel.PullingDownVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)]
    [TemplateVisualState(Name = PullDownToRefreshPanel.ReadyToReleaseVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)]
    [TemplateVisualState(Name = PullDownToRefreshPanel.RefreshingVisualState, GroupName = PullDownToRefreshPanel.ActivityVisualStateGroup)]
    public class PullDownToRefreshPanel : Control
    {
        #region Visual state name constants

        private const string ActivityVisualStateGroup = "ActivityStates";
        private const string InactiveVisualState = "Inactive";
        private const string PullingDownVisualState = "PullingDown";
        private const string ReadyToReleaseVisualState = "ReadyToRelease";
        private const string RefreshingVisualState = "Refreshing";

        #endregion

        #region Fields
        private ScrollViewer targetScrollViewer;
        #endregion

        #region Event
        public event EventHandler RefreshRequested;
        #endregion


        public PullDownToRefreshPanel()
        {
            this.DefaultStyleKey = typeof(PullDownToRefreshPanel);
            this.LayoutUpdated += PullDownToRefreshPanel_LayoutUpdated;
        }

        void PullDownToRefreshPanel_LayoutUpdated(object sender, EventArgs e)
        {
            if (this.targetScrollViewer == null)
            {
                this.targetScrollViewer = FindVisualElement<ScrollViewer>(VisualTreeHelper.GetParent(this));
                if (this.targetScrollViewer != null)
                {
                    this.targetScrollViewer.MouseMove += targetScrollViewer_MouseMove;
                    this.targetScrollViewer.MouseLeftButtonUp += targetScrollViewer_MouseLeftButtonUp;
                    if (this.targetScrollViewer.ManipulationMode != ManipulationMode.Control)
                    {
                        throw new InvalidOperationException("requires the scrollviewer to have ManipulationMode=Control. (ListBoxes may require re-templating）.");
                    }
                }
            }
            
        }

        #region PullDownOperation

        void targetScrollViewer_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            UIElement scrollContent = (UIElement)this.targetScrollViewer.Content;
            CompositeTransform ct = scrollContent.RenderTransform as CompositeTransform;
            if (ct != null && !this.IsRefreshing)
            {
                VisualStateManager.GoToState(this, PullDownToRefreshPanel.InactiveVisualState, false);
                this.PullDistance = 0;
                this.PullFraction = 0;
                EventHandler hanlder = this.RefreshRequested;
                if (hanlder != null)
                {
                    hanlder(this, EventArgs.Empty);
                }
            }
        }

        void targetScrollViewer_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            UIElement scrollContent = (UIElement)this.targetScrollViewer.Content;
            CompositeTransform ct = scrollContent.RenderTransform as CompositeTransform;
            if (ct != null && !this.IsRefreshing)
            {
                string activityState;
                if (ct.TranslateY > this.PullThreshold)
                {
                    this.PullDistance = ct.TranslateY;
                    this.PullFraction = 1.0;
                    activityState = PullDownToRefreshPanel.ReadyToReleaseVisualState;
                }
                else if (ct.TranslateY > 0)
                {
                    this.PullDistance = ct.TranslateY;
                    double threshold = this.PullThreshold;
                    this.PullFraction = threshold == 0.0 ? 1.0 : Math.Min(1.0, ct.TranslateY / threshold);
                    activityState = PullDownToRefreshPanel.PullingDownVisualState;
                }
                else
                {
                    this.PullDistance = 0;
                    this.PullFraction = 0;
                    activityState = PullDownToRefreshPanel.InactiveVisualState;
                }
                VisualStateManager.GoToState(this, activityState, false);
            }
        }

        #endregion
       
        #region DependencyProperty

        #region IsRefreshingProperty

        public bool IsRefreshing
        {
            get { return (bool)GetValue(IsRefreshingProperty); }
            set { SetValue(IsRefreshingProperty, value); }
        }

        public static readonly DependencyProperty IsRefreshingProperty =
            DependencyProperty.Register("IsRefreshing", typeof(bool), typeof(PullDownToRefreshPanel), new PropertyMetadata(false, new PropertyChangedCallback(OnIsRefreshingChanged)));

        private static void OnIsRefreshingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((PullDownToRefreshPanel)d).OnIsRefreshingChanged(e);

        }

        private void OnIsRefreshingChanged(DependencyPropertyChangedEventArgs e)
        {
            string activityState = (bool)e.NewValue ? PullDownToRefreshPanel.RefreshingVisualState : PullDownToRefreshPanel.InactiveVisualState;
            VisualStateManager.GoToState(this, activityState, false);
        }

        #endregion

        #region PullThreshold DependencyProperty

        public double PullThreshold
        {
            get { return (double)GetValue(PullThresholdProperty); }
            set { SetValue(PullThresholdProperty, value); }
        }

        public static readonly DependencyProperty PullThresholdProperty =
            DependencyProperty.Register("PullThreshold", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(100.0));

        #endregion

        #region PullDistance DependencyProperty

        public double PullDistance
        {
            get { return (double)GetValue(PullDistanceProperty); }
            set { SetValue(PullDistanceProperty, value); }
        }

        public static readonly DependencyProperty PullDistanceProperty =
            DependencyProperty.Register("PullDistance", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(0.0));


        #endregion

        #region PullFraction DependencyProperty

        public double PullFraction
        {
            get { return (double)GetValue(PullFractionProperty); }
            set { SetValue(PullFractionProperty, value); }
        }

        public static readonly DependencyProperty PullFractionProperty =
            DependencyProperty.Register("PullFraction", typeof(double), typeof(PullDownToRefreshPanel), new PropertyMetadata(0.0));


        #endregion

        #region PullingDownTemplate DependencyProperty

        public DataTemplate PullingDownTemplate
        {
            get { return (DataTemplate)GetValue(PullingDownTemplateProperty); }
            set { SetValue(PullingDownTemplateProperty, value); }
        }

        public static readonly DependencyProperty PullingDownTemplateProperty =
            DependencyProperty.Register("PullingDownTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);

        #endregion

        #region ReadyToReleaseTemplate DependencyProperty

        public DataTemplate ReadyToReleaseTemplate
        {
            get { return (DataTemplate)GetValue(ReadyToReleaseTemplateProperty); }
            set { SetValue(ReadyToReleaseTemplateProperty, value); }
        }

        public static readonly DependencyProperty ReadyToReleaseTemplateProperty =
            DependencyProperty.Register("ReadyToReleaseTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);

        #endregion

        #region RefrehingTemplate DependencyProperty


        //public DataTemplate RefreshingTemplate
        //{
        //    get { return (DataTemplate)GetValue(RefreshingTemplateProperty); }
        //    set { SetValue(RefreshingTemplateProperty, value); }
        //}

        //public static readonly DependencyProperty RefreshingTemplateProperty =
        //    DependencyProperty.Register("RefreshingTemplate", typeof(DataTemplate), typeof(PullDownToRefreshPanel), null);


        #endregion

        #endregion

        #region UtilMethod

        /// <summary>
        /// Performs a breadth-first search of all elements in the container,
        /// and returns the first element encountered that has type T.
        /// </summary>
        /// <typeparam name="T">Type of element to search for.</typeparam>
        /// <param name="initial">The container to search</param>
        /// <returns>Element of the requested type, or null if none was found.</returns>
        private static T FindVisualElement<T>(DependencyObject container) where T : DependencyObject
        {
            Queue<DependencyObject> childQueue = new Queue<DependencyObject>();
            childQueue.Enqueue(container);

            while (childQueue.Count > 0)
            {
                DependencyObject current = childQueue.Dequeue();

                T result = current as T;
                if (result != null && result != container)
                {
                    return result;
                }

                int childCount = VisualTreeHelper.GetChildrenCount(current);
                for (int childIndex = 0; childIndex < childCount; childIndex++)
                {
                    childQueue.Enqueue(VisualTreeHelper.GetChild(current, childIndex));
                }
            }

            return null;
        }
        #endregion
    }
}
